// Copyright 2012 Google Inc. All Rights Reserved.

package com.google.appengine.preferencessync;

import com.google.appengine.preferencessync.adapter.SyncableSharedPreferencesProvider;
import com.google.appengine.preferencessync.internal.Config;
import com.google.appengine.preferencessync.internal.Config.MergeResolutionType;
import com.google.appengine.preferencessync.internal.SyncClientLog;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ProviderInfo;
import android.os.Bundle;
import android.os.Process;

import java.util.List;

/**
 * Utility class for adding shared preferences sync.
 */
public class SyncUtils {

  private static final String ACCOUNT_DELETED_BEFORE_CALLING_SYNC =
      "Account with name %s deleted before calling sync";
  private static final String PROVIDER_NOT_FOUND = "Provider with name %s not found.";
  private static final String ACCOUNT_TYPE = "com.google";
  private static final String PROVIDER_NAME =  SyncableSharedPreferencesProvider.class.getName();

  /**
   * Conflict types the user can use.
   */
  public enum ConflictResolutionType {
    SERVER_WINS(MergeResolutionType.SERVER_WINS),
    CLIENT_WINS(MergeResolutionType.CLIENT_WINS),
    CUSTOM_WINS(MergeResolutionType.CUSTOM_WINS);

    // Preferences property associated with the strategy.
    private final MergeResolutionType mergeResolutionType;

    private ConflictResolutionType(MergeResolutionType type) {
      this.mergeResolutionType = type;
    }

    // Only to be used by the parent class.
    // Therefore private.
    private MergeResolutionType getResolutionType() {
      return this.mergeResolutionType;
    }
  }

  /**
   * Add preferences to the list of preferences to be synced.
   */
  public static void addPreferences(Context context, String preferences) {
    Config.getDefaultConfig(context).addPreferencesToSync(preferences,
        ConflictResolutionType.SERVER_WINS.getResolutionType());
  }

  /**
   * Remove preferences from the list of preferences to be synced.
   */
  public static void removePreferences(Context context, String preferences) {
    Config.getDefaultConfig(context).removePreferencesToSync(preferences);
  }

  /**
   * Add preferences with a particular conflict resolution type.
   */
  public static void addPreferences(
      Context context, String preferences, ConflictResolutionType type) {
    Config.getDefaultConfig(context).addPreferencesToSync(preferences, type.getResolutionType());
  }

  /**
   * Set sync server URL.
   */
  public static void setSyncServerUrl(Context context, String rootUrl) {
    Config.getDefaultConfig(context).setRootUrl(rootUrl);
  }

  /**
   * Set username from which to sync.
   *
   * @param username the name of a google account one wants to sync using.
   */
  public static void setUsername(Context context, String username) {
    Config.getDefaultConfig(context).setUsername(username);
  }

  /**
   * Get username from which to sync.
   */
  public static String getUsername(Context context) {
    return Config.getDefaultConfig(context).getUsername();
  }

  /**
   * Call Sync asynchronously.
   */
  public static void doSync(Context context) {
    Config config = Config.getDefaultConfig(context);
    if (!config.isSane()) {
      return;
    }

    String authority = getAuthority(context, PROVIDER_NAME);
    if (authority == null) {
      // Log error in logcat.
      SyncClientLog.logError(SyncUtils.class.getClass(),
          String.format(PROVIDER_NOT_FOUND, PROVIDER_NAME));
      return;
    }

    Account account = getAccount(context, config.getUsername());
    if (account == null) {
      // This should happen in a very rare case we have already checked whether the config is sane.
      // I.e when the account gets deleted when this code is executing.
      SyncClientLog.logError(SyncUtils.class.getClass(),
          String.format(ACCOUNT_DELETED_BEFORE_CALLING_SYNC, config.getUsername()));
      return;
    }

    SyncClientLog.logDebug(SyncUtils.class,
        String.format("Calling sync for account %s and authority %s", account.name, authority));
    ContentResolver.requestSync(account, authority, new Bundle());
    return;
  }

  /**
   * Get the account corresponding to the username.
   * @param username This is the name of a google account one wants to sync using
   */
  private static Account getAccount(Context context, String username) {
    if (username == null) {
      return null;
    }
    Account[] accounts = AccountManager.get(context).getAccountsByType(ACCOUNT_TYPE);
    for (Account account : accounts) {
      if (account.name.equals(username)) {
        return account;
      }
    }
    return null;
  }

  /**
   * Get authority for the given provider.
   */
  private static String getAuthority(Context context, String providerName) {
    // Get all providers.
    List<ProviderInfo> providers = context.getPackageManager()
        .queryContentProviders(context.getPackageName(), Process.myUid(), 0);

    for (ProviderInfo provider : providers) {
      if (provider.name.equals(providerName)) {
        return provider.authority;
      }
    }
    return null;
  }

  /**
   * Add periodic sync to sync adapter. Also sets the username in config.
   * @param username the name of a google account one wants to sync using
   * @param pollFrequency in seconds.
   */
  public static void addPeriodicSync(Context context, String username, long pollFrequency) {
    setUsername(context, username);
    Account account = getAccount(context, username);
    if (account == null) {
      throw new IllegalArgumentException(String.format("Account for %s does not exists", username));
    }
    ContentResolver.addPeriodicSync(account, getAuthority(context, PROVIDER_NAME), new Bundle(),
        pollFrequency);
  }

  /**
   * Set the gcm device registration Id.
   */
  public static void setGCMRegistrationId(Context context, String registration) {
    Config.getDefaultConfig(context).setGCMRegistrationId(registration);
  }

  /**
   * @return the gcm device registration Id. null if not stored.
   */
  public static String getGCMRegistrationId(Context context) {
    return Config.getDefaultConfig(context).getGCMRegistrationId();
  }
}
